package test.base.medium.ffmpeg;

import java.awt.image.BufferedImage;
import static org.bytedeco.ffmpeg.global.avcodec.av_packet_unref;
import static org.bytedeco.ffmpeg.global.avcodec.avcodec_alloc_context3;
import static org.bytedeco.ffmpeg.global.avcodec.avcodec_close;
import static org.bytedeco.ffmpeg.global.avcodec.avcodec_find_decoder;
import static org.bytedeco.ffmpeg.global.avcodec.avcodec_free_context;
import static org.bytedeco.ffmpeg.global.avcodec.avcodec_open2;
import static org.bytedeco.ffmpeg.global.avcodec.avcodec_parameters_to_context;
import static org.bytedeco.ffmpeg.global.avcodec.avcodec_receive_frame;
import static org.bytedeco.ffmpeg.global.avcodec.avcodec_send_packet;
import static org.bytedeco.ffmpeg.global.avformat.av_dump_format;
import static org.bytedeco.ffmpeg.global.avformat.av_read_frame;
import static org.bytedeco.ffmpeg.global.avformat.avformat_close_input;
import static org.bytedeco.ffmpeg.global.avformat.avformat_find_stream_info;
import static org.bytedeco.ffmpeg.global.avformat.avformat_open_input;
import static org.bytedeco.ffmpeg.global.avutil.AVMEDIA_TYPE_VIDEO;
import static org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_BGR24;
import static org.bytedeco.ffmpeg.global.avutil.av_frame_alloc;
import static org.bytedeco.ffmpeg.global.avutil.av_free;
import static org.bytedeco.ffmpeg.global.avutil.av_image_get_buffer_size;
import static org.bytedeco.ffmpeg.global.avutil.av_malloc;
import static org.bytedeco.ffmpeg.global.swscale.SWS_BILINEAR;
import static org.bytedeco.ffmpeg.global.swscale.sws_freeContext;
import static org.bytedeco.ffmpeg.global.swscale.sws_getContext;
import static org.bytedeco.ffmpeg.global.swscale.sws_scale;

import org.bytedeco.ffmpeg.avcodec.AVCodec;
import org.bytedeco.ffmpeg.avcodec.AVCodecContext;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.avformat.AVStream;
import org.bytedeco.ffmpeg.avutil.AVDictionary;
import org.bytedeco.ffmpeg.avutil.AVFrame;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.ffmpeg.swscale.SwsContext;
import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.javacpp.DoublePointer;
import org.bytedeco.javacpp.Pointer;
import org.bytedeco.javacpp.PointerPointer;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;

import hyl.base.medium.MyUI;
import hyl.core.run.IDo1;
import hyl.core.run.IFilter;
import hyl.core.run.MyRun;

/**
 * 分辨率（kbps）＝文件大小（KB）＊8／时间（秒）
 * 
 * 
 * @author 37798955@qq.com
 *
 */
public class Rtsp {
	public final static MyRun 推送线程池 = MyRun.getInstance("推送流媒体", 128);
	public final static MyRun 录制线程池 = MyRun.getInstance("录制流媒体", 32);
	public final static MyRun 采集线程池 = MyRun.getInstance("采集流媒体", 128);
	/**
	 * 可以处理的视频格式
	 */
	public final static String[] VIDEO_TYPE = { "MP4", "WMV" };
	/**
	 * 可以处理的图片格式
	 */
	public final static String[] IMAGE_TYPE = { "JPG", "JPEG", "PNG", "GIF" };
	/**
	 * recoder 只支持flv，mp4，3gp和avi四种格式，
	 */
	public final static String[] VIDEO_RECODER = { "flv", "mp4", "3gp", "avi" };
	// flv:AV_CODEC_ID_FLV1;mp4:AV_CODEC_ID_MPEG4;3gp:AV_CODEC_ID_H263;avi:AV_CODEC_ID_HUFFYUV;
	// public final static int[] VIDEO_CODEC_RECODER = { AV_CODEC_ID_FLV1,
	// AV_CODEC_ID_MPEG4, AV_CODEC_ID_H263,AV_CODEC_ID_HUFFYUV};
	/**
	 * 可以处理的音频格式
	 */
	public final static String[] AUDIO_TYPE = { "AAC" };
	public final static int I分辨率 = 76800;

	final static String TCP = "tcp";// "tcp";

	/**
	 * 表示经过编码（压缩）后的音频数据每秒钟需要用多少个比特来表示，<br>
	 * 比特率与音频压缩的关系简单的说就是比特率越高音质就越好，但编码后的文件就越大；
	 */

	public static final Logger log = LoggerFactory.getLogger(Rtsp.class);

	// 队列无用:因为AVPacket 在c++中实现 ,有着自己独有的 内存回收释放机制 ,无法通过java jvm 管理
	// public ConcurrentLinkedQueue<AVPacket> 包队列 = new
	// ConcurrentLinkedQueue<AVPacket>();
	// public ConcurrentLinkedQueue<AVPacket> 帧队列 = new
	// ConcurrentLinkedQueue<Frame>();
	/**
	 * 录制函数可以用于<br>
	 * 1)把rtsp 推送到rtmp 服务器<br>
	 * 2)录制视频文件 <br>
	 * 
	 * @param nowThread
	 */
	public static void run录制视频(String rtsp路径, String 输出路径, int 录制帧数) {
		采集线程池.execute(() -> {

			// 抓帧器
			FFmpegFrameGrabber grabber = null;
			// 录制器
			FFmpegFrameRecorder recorder = null;
			try {
				// 抓帧器
				grabber = new FFmpegFrameGrabber(rtsp路径);
				// tcp用于解决丢包问题,如果不担心丢包可以用udp
				// grabber.setOption("rtsp_transport", TCP);
				grabber.start();
				// 录制器
				recorder = new FFmpegFrameRecorder(输出路径, grabber.getImageWidth(), grabber.getImageHeight());
				recorder.setFormat("flv");
				//recorder.setVideoOption("preset", "ultrafast");
				AVFormatContext fc = grabber.getFormatContext();

				// fc.max_interleave_delta(100);
				// 基本不需要设置,所有设置在start()中已经实现
				// 通过这总方式开启时 基本不需要设置参数，因为所有参数与输入相同
				recorder.start(fc);
			} catch (IOException e1) {
				e1.printStackTrace();
			}

			//System.out.println(输出路径 + "推流开始...");
			long err_index = 0;// 采集或推流导致的错误次数
			// 连续五次没有采集到帧则认为视频采集结束，程序错误次数超过5次即中断程序
			// 将探测时留下的数据帧释放掉，以免因为dts，pts的问题对推流造成影响
			long i = 0;

			while (err_index < 10 && (录制帧数 == 0 || i < 录制帧数)) {
				try {
					// 获取没有解码的音视频帧
					AVPacket pkt = grabber.grabPacket();
					if (pkt == null || pkt.size() <= 0 || pkt.data() == null) {
						// 空包记录次数跳过
						err_index++;
						log.error("出现空帧 ," + rtsp路径);
						continue;
					}
					//System.out.println("1");
					// if (i%2==0) {
					if (!recorder.recordPacket(pkt)) {
						err_index += 1;
						log.error("无法录制帧 ," + rtsp路径);
					}
					// }
					av_packet_unref(pkt);
				} catch (IOException e) {
					log.error("接口异常," + e);
					err_index++;
				}
				i++;
			}
			close抓取器(grabber);
			close录制器(recorder);
			System.out.println("录制结束...");
		});

	}

	/**
	 * 推送图片（摄像机直播）<br>
	 * 用一个单独的线程每间隔n帧采集一次
	 */
	public static void run采集(String rtsp路径, int 间隔几帧, final double d伽马值, IDo1<BufferedImage> call) {

		采集线程池.execute(() -> {
			// 把frame 转为 image 还可以修正颜色偏差,
			Java2DFrameConverter converter = new Java2DFrameConverter();

			// 抓帧器
			FFmpegFrameGrabber grabber = null;
			// 获取视频源
			try {

				grabber = FFmpegFrameGrabber.createDefault(rtsp路径);
				// tcp用于解决丢包问题,如果不担心丢包可以用udp
				grabber.setOption("rtsp_transport", TCP);
				// grabber.flush();
				grabber.start();

			} catch (IOException e1) {
				e1.printStackTrace();
				log.error("do采集图片创建 抓帧器失败");
			}
			if (grabber == null)
				return;
			try {
				int j = 0;

				while (true) {
					Frame frame = grabber.grabImage();
					// System.out.println("22");
					if (null == frame) {
						continue;
					}
					// System.out.println("-");
					// BufferedImage bufferedImage = java2DFrameConverter.getBufferedImage(frame);
					// byte[] bytes = MyByte.image2Bytes(bufferedImage, "jpg");
					// converter.getBufferedImage(frame, gamma)
					// converter.getBufferedImage(image, converter.getBufferedImageType(image) ==
					// BufferedImage.TYPE_CUSTOM ? 1.0 : inverseGamma, flipChannels, null)

					if ((++j) == 间隔几帧) {
						j = 0;
						BufferedImage bufferedImage = converter.getBufferedImage(frame, d伽马值);
						call.run(bufferedImage);
					}
					// 使用websocket发送视频帧数据
					// WebSocketServer.sendAllByObject(new Image(bytes));
				}
			} catch (FrameGrabber.Exception | RuntimeException e) {
				log.error("因为异常，grabber关闭，rtsp连接断开，尝试重新连接");
				log.error("exception : ", e);
			}
			close抓取器(grabber);

		});
	}

	static void close抓取器(FFmpegFrameGrabber grabber) {
		if (grabber == null)
			return;
		try {
			grabber.stop();
		} catch (FrameGrabber.Exception ex) {
			log.error("grabber 异常停止: ", ex);
		} finally {
			try {
				grabber.release();
				grabber.close();
			} catch (org.bytedeco.javacv.FrameGrabber.Exception e1) {
				e1.printStackTrace();
			}
			grabber = null;
		}
	}

	static void close录制器(FFmpegFrameRecorder recorder) {
		if (recorder == null)
			return;
		try {
			recorder.stop();
		} catch (org.bytedeco.javacv.FrameRecorder.Exception ex) {
			log.error("recorder 异常停止: ", ex);
		} finally {
			try {
				recorder.release();
				recorder.close();
			} catch (org.bytedeco.javacv.FrameRecorder.Exception e1) {
				e1.printStackTrace();
			}
			recorder = null;
		}
	}

	public static ByteBuffer saveFrame(AVFrame pFrame) {
		BytePointer data = pFrame.data(0);
		int size = pFrame.width() * pFrame.height() * 3;
		ByteBuffer buf = data.position(0).limit(size).asBuffer();
		return buf;
	}

	public static BufferedImage avFrame2Img(AVFrame pFrame) {
		ByteBuffer src = saveFrame(pFrame);
		return MyUI.byteBuffer2BGR(src, pFrame.width(), pFrame.height());
	}

	public static void run遍历RGB帧(final String 打开路径, int 间隔帧, int 输出宽, int 输出高, IFilter<BufferedImage> 过滤器) {
		采集线程池.execute(() -> {
			int 输出格式 = AV_PIX_FMT_BGR24;
			try {
				String msg = run遍历帧(打开路径, 输出格式, 间隔帧, 输出宽, 输出高, 过滤器);
				System.out.println(msg);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		});
	}

	
	
	// , String out_file
	public static String run遍历帧(final String 打开路径, int 输出格式, int 间隔帧, int 输出宽, int 输出高, IFilter<BufferedImage> 过滤器)
			throws IOException {

		AVFormatContext pFormatCtx = new AVFormatContext(null);
		int videoStream;

		// 解码器
		AVCodec 解码器 = null;
		// 解码器的上下文,除了解码器,还有解码时候需要用到的其他额外信息
		AVCodecContext pCodecCtx = null;

		AVDictionary 解码器配置项 = null;
		// int 输入格式 = AV_PIX_FMT_YUVJ420P;

		//
		BytePointer buffer = null;
		SwsContext sws_ctx = null;
		//ssl时才需要开启网络解析
		// avformat_network_init();
		// 读取视频文件
		//主要就是根据url创建InputStream，并且会根据不同协议（rtmp/rtsp/hls/文件等）尝试读取一些文件头数据（流媒体头数据）。
		if (avformat_open_input(pFormatCtx, 打开路径, null, null) != 0) {
			return "无法打开" + 打开路径; // Couldn't open file

		}

		// 查找视频信息
		if (avformat_find_stream_info(pFormatCtx, (PointerPointer<Pointer>) null) < 0) {
			return "无法发现视频流信息"; // Couldn't find stream information
		}
		/*
		 * 打印有关输入或输出格式的详细信息，例如<br> 持续时间，比特率，流，容器，程序，元数据，边数据，<br> 编解码器和时基。
		 */
		av_dump_format(pFormatCtx, 0, 打开路径, 0);// Dump information about file onto standard error

		// 发现关键帧
		videoStream = -1;
		AVStream stream = null;
		for (int i = 0; i < pFormatCtx.nb_streams(); i++) {
			stream = pFormatCtx.streams(i);
			// stream.codecpar().codec_id();
			// stream.codecpar().codec_type()
			if (stream.codecpar().codec_type() == AVMEDIA_TYPE_VIDEO) {
				videoStream = i;
				break;
			}
		}
		if (videoStream == -1) {
			return "找不到关键帧,在" + 打开路径;
		}

		// 从关键帧 获取视频编码
		// pCodecCtx = pFormatCtx.streams(videoStream).codec();

		// 查找解码器
		// pCodec = avcodec_find_decoder(pCodecCtx.codec_id());
		int 编码 = stream.codecpar().codec_id();
		// 查找解码器
		解码器 = avcodec_find_decoder(编码);
		if (解码器 == null) {
			return "缺少解码器:编码=" + 编码;
		}
		// 从解码器获取解码器对象
		pCodecCtx = avcodec_alloc_context3(解码器);// 需要使用avcodec_free_context释放
		// 事实上codecpar包含了大部分解码器相关的信息，这里是直接从AVCodecParameters复制到AVCodecContext
		avcodec_parameters_to_context(pCodecCtx, stream.codecpar());
		// av_codec_set_pkt_timebase(pCodecCtx, stream->time_base);

		// 打开解码器
		if (avcodec_open2(pCodecCtx, 解码器, 解码器配置项) < 0) {
			return "无法打开解码器:编码=" + 编码;
		}

		/*
		 * 分配一个AVFrame并将其字段设置为默认值。<br>
		 * 
		 * 结果必须使用av_frame_free（）释放结构。<br>
		 * 
		 * @返回一个用默认值填充的AVFrame或失败时为NULL。<br>
		 * 
		 * 注意这只分配AVFrame本身，而不分配数据缓冲区。<br>
		 * 
		 * 数据缓冲区通过使用av_frame_get_buffer（）或其他方式分配<br>
		 * 
		 * 
		 */
		// pFrame 用于存放解码后的 avframe
		AVFrame pFrame = av_frame_alloc();// 分配帧内存
		// 确定图像所需的缓冲区大小, 并分配缓冲区
		// pFrameRGB 存放解码帧变换输出的 RGB格式帧
		AVFrame pFrameRGB = av_frame_alloc();// 分配帧内存
		if (pFrameRGB == null) {
			return "无法分配avframe";
		}
		int 宽 = pCodecCtx.width(), 高 = pCodecCtx.height();
		pFrameRGB.width(宽);
		pFrameRGB.height(高);
		pFrameRGB.format(输出格式);

		/*
		 * SwsContext sws_ctx 图像变换器 ,你需要它来表演缩放等操作
		 * 
		 * sws_getContext () 分配并返回一个SwsContext。
		 * 
		 * 使用sws_scale（）进行缩放/转换操作。
		 */
		sws_ctx = sws_getContext(宽, 高, pCodecCtx.pix_fmt(), 宽, 高, 输出格式, SWS_BILINEAR, null, null, (DoublePointer) null);
		// av_image_get_buffer_size 函数的作用是通过指定像素格式、图像宽、图像高来计算所需的内存大小
		// 旧的写法 numBytes = avpicture_get_size(图像格式, width, height);
		int numBytes = av_image_get_buffer_size(输出格式, 宽, 高, 1);
		/*
		 * 分配一个适合所有内存访问的对齐内存块（包括CPU上可用的向量）。
		 */
		buffer = new BytePointer(av_malloc(numBytes));
		// 将缓冲区的适当部分分配给pFrameRGB中的图像平面,把buffer 分配给pFrameRGB.data()
		// 请注意，pFrameRGB是一个AVFrame，但AVFrame是一个AVPicture的超集
		// 过期 avpicture_fill(new AVPicture(pFrameRGB), buffer, 图像格式,
		// pCodecCtx.width(),pCodecCtx.height());
		avutil.av_image_fill_arrays(pFrameRGB.data(), pFrameRGB.linesize(), buffer, 输出格式, 宽, 高, 1);

		// 新建canvasFrame窗口，命名
//	        CanvasFrame canvasFrame = new CanvasFrame("测试点击");
//	        // 关闭窗口则退出程序
//	        canvasFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//		
//	        canvasFrame.setAlwaysOnTop(true);
		// canvasFrame.showImage(converter.convert(mat));
		// ImageFrame iframe = new ImageFrame(会话id, 输出宽, 输出高);
		AVPacket packet = new AVPacket();
		int j = 0;
		// 读视频帧一个帧
		while (av_read_frame(pFormatCtx, packet) >= 0) {
			// 如果是图像帧
			if (packet.stream_index() == videoStream) {
				// 解码视频帧
				// 将AVPKT大小的视频帧从AVPKT>数据解码为图片。
				//// 一些解码器可以支持单个av包中的多个帧，例如解码器将只解码第一帧。
				// 函数 avcodec_decode_video2 已经过时了，现在推荐使用的是 avcodec_send_packet() 和
				// avcodec_receive_frame()，
				// 而在新版本中，avcodec_decode_video2 也是通过这两个新的 API 完成解码功能的。
				// 读取到一帧音频或者视频 packet

				int re = avcodec_send_packet(pCodecCtx, packet);
				if (re != 0)
					break;
				while (avcodec_receive_frame(pCodecCtx, pFrame) == 0) {
					// 处理解码后音视频 pFrame
					sws_scale(sws_ctx, pFrame.data(), pFrame.linesize(), 0, 高, pFrameRGB.data(), pFrameRGB.linesize());
					BufferedImage img = avFrame2Img(pFrameRGB);
					if (j == 0) {
						if (过滤器.where(img))
							return "运行结束";
					}
					j++;
					if (j == 间隔帧) {
						j = 0;
					}

				}

			}
		}
		// 手动释放内存
		av_packet_unref(packet);// Free the packet that was allocated by av_read_frame
		// av_free(buffer);// buffer实现了自动关闭接口 可以不管它
		av_free(pFrameRGB);// Free the RGB image
		av_free(pFrame);// Free the YUV frame
		avcodec_free_context(pCodecCtx);
		sws_freeContext(sws_ctx);// Free SwsContext
		// 关闭解码器
		avcodec_close(pCodecCtx);// Close the codec
		// 关闭图像源
		avformat_close_input(pFormatCtx);// Close the video file

		return "完毕";
	}
}
